#pragma once
//我们所写的这个程序是一个网络程序，udp服务，可以接受报文

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <strings.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"
enum{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};

const static int defaultfd = -1;
class UdpServer
{
public:
    UdpServer(uint16_t port):_port(port)
    {
        _isrunning = true;
        _sockfd = defaultfd;
    }

    void InitServer()
    {
        //1.创建套接字文件描述符 udp_socket
        //2号
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd < 0)
        {
            LOG(FATAL,"socket create error,errno:%d:%s\n", errno,strerror(errno));
            exit(SOCKET_ERROR);
        }
        LOG(INFO,"socket create success,sockfd:%d\n",_sockfd);
        //2.到这一步套接字文件创建成功，需要将套接字文件和ip + port关联
        struct sockaddr_in local;//可以认为这是套接字类型，有很多种
        //套接字类型是用来把套接字文件和ip+port关联起来的。
        //这种定义本质是就是在数据栈上定义的结构体


        bzero(&local,sizeof(local));

        local.sin_family = AF_INET;//ipv4
        //此处是本地序列转网络序列
        //3号
        local.sin_port = htons(_port);

        //ip地址由字符串转4字节数据
        //再由主机序列转网络序列
        //man inet_addr,inet代表网络序列,addr代表字符串ip
        //in_addr_t inet_addr(const char* cp);
        
        //3号
        local.sin_addr.s_addr = INADDR_ANY;

        //3.到这一步，套接字文件有了，套接字有了，接下来就是绑定
        //2号
        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n < 0)
        {
            LOG(FATAL,"bind errror,errno:%d:%s\n",errno,strerror(errno));
            exit(BIND_ERROR);
        }
        LOG(INFO,"bind success\n");
    }

    void Start()
    {
        //服务器都是死循环,UDP是面向数据报的协议
        _isrunning =true;
        while(1)
        {
            //udp服务启动后，做的工作无非就是接受报文、发送报文
            //1.接受报文的逻辑
            char buffer[1024];
            //服务器做的工作是，接受报文，发送报文
            //一个端口号只能绑定一个服务（进程），但是进程可以绑定多个ip

            struct sockaddr_in peer;//从远端机器的套接字接收
            socklen_t len = sizeof(peer);
            //2号
            ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
            if(n > 0)
            {
                //现在已经接受成功了，可以尝试将接受主机的信息打印出来，这些信息全在
                //sockaddr_in这个结构体变量中
                buffer[n] = 0;
                InetAddr addr(peer);
                LOG(INFO,"receive info from [%s:%d]:%s\n",addr.Ip().c_str(),addr.Port(),buffer);

                //2.每次收到一个报文，就要对这个收到的报文作响应echo
                //2号手册
                //解释一下这里是发送，我们简单处理,收到什么发什么，用strlen是以字符0结尾
                sendto(_sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);

            }
        }
        _isrunning = false;
    }

private:
   int _sockfd;//套接字文件描述符->对应当前进程
    uint16_t _port;//这个端口号，绑定在当前进程，其实是和这个进程的特定套接字绑定
    //关于端口号，一个进程可以有多个端口号
    bool _isrunning;

};




// #pragma once
// //我们所写的这个程序是一个网络程序，udp服务，可以接受报文

// #include <iostream>
// #include <string>
// #include <cerrno>
// #include <cstring>
// #include <strings.h>

// #include <sys/types.h>
// #include <sys/socket.h>
// #include <arpa/inet.h>
// #include <netinet/in.h>
// #include "Log.hpp"
// #include "InetAddr.hpp"
// enum{
//     SOCKET_ERROR = 1,
//     BIND_ERROR,
//     USAGE_ERROR
// };

// const static int defaultfd = -1;
// class UdpServer
// {
// public:
//     UdpServer(const std::string& ip,uint16_t port):_ip(ip),_port(port)
//     {
//         _isrunning = true;
//         _sockfd = defaultfd;
//     }

//     void InitServer()
//     {
//         //1.创建套接字文件描述符 udp_socket
//         //2号
//         _sockfd = socket(AF_INET,SOCK_DGRAM,0);
//         if(_sockfd < 0)
//         {
//             LOG(FATAL,"socket create error,errno:%d:%s\n", errno,strerror(errno));
//             exit(SOCKET_ERROR);
//         }
//         LOG(INFO,"socket create success,sockfd:%d\n",_sockfd);
//         //2.到这一步套接字文件创建成功，需要将套接字文件和ip + port关联
//         struct sockaddr_in local;//可以认为这是套接字类型，有很多种
//         //套接字类型是用来把套接字文件和ip+port关联起来的。
//         //这种定义本质是就是在数据栈上定义的结构体


//         bzero(&local,sizeof(local));

//         local.sin_family = AF_INET;//ipv4
//         //此处是本地序列转网络序列
//         //3号
//         local.sin_port = htons(_port);

//         //ip地址由字符串转4字节数据
//         //再由主机序列转网络序列
//         //man inet_addr,inet代表网络序列,addr代表字符串ip
//         //in_addr_t inet_addr(const char* cp);
        
//         //3号
//         local.sin_addr.s_addr = inet_addr(_ip.c_str());

//         //3.到这一步，套接字文件有了，套接字有了，接下来就是绑定
//         //2号
//         int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
//         if(n < 0)
//         {
//             LOG(FATAL,"bind errror,errno:%d:%s\n",errno,strerror(errno));
//             exit(BIND_ERROR);
//         }
//         LOG(INFO,"bind success\n");
//     }

//     void Start()
//     {
//         //服务器都是死循环,UDP是面向数据报的协议
//         _isrunning =true;
//         while(1)
//         {
//             //udp服务启动后，做的工作无非就是接受报文、发送报文
//             //1.接受报文的逻辑
//             char buffer[1024];
//             //服务器做的工作是，接受报文，发送报文
//             //一个端口号只能绑定一个服务（进程），但是进程可以绑定多个ip

//             struct sockaddr_in peer;//从远端机器的套接字接收
//             socklen_t len = sizeof(peer);
//             //2号
//             ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);
//             if(n > 0)
//             {
//                 //现在已经接受成功了，可以尝试将接受主机的信息打印出来，这些信息全在
//                 //sockaddr_in这个结构体变量中
//                 buffer[n] = 0;
//                 InetAddr addr(peer);
//                 LOG(INFO,"receive info from [%s:%d]:%s\n",addr.Ip().c_str(),addr.Port(),buffer);

//                 //2.每次收到一个报文，就要对这个收到的报文作响应echo
//                 //2号手册
//                 //解释一下这里是发送，我们简单处理,收到什么发什么，用strlen是以字符0结尾
//                 sendto(_sockfd,buffer,strlen(buffer),0,(struct sockaddr*)&peer,len);

//             }
//         }
//         _isrunning = false;
//     }

// private:
//    int _sockfd;//套接字文件描述符->对应当前进程
//     std::string _ip;//服务器中，这个进程的套接字要绑定的本地ip地址
//     uint16_t _port;//这个端口号，绑定在当前进程，其实是和这个进程的特定套接字绑定
//     //关于端口号，一个进程可以有多个端口号
//     bool _isrunning;

// };